﻿namespace ChessToys
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;

    /// <summary>
    /// The chess board.
    /// </summary>
    public class Board : IEnumerable<Position>
    {
        /// <summary>
        /// Create an empty <see cref="Board"/>.
        /// </summary>
        /// <returns>An empty <see cref="Board"/>.</returns>
        public static Board Empty()
        {
            return new Board(8);
        }

        /// <summary>
        /// Create a <see cref="Board"/> build default.
        /// </summary>
        /// <returns>A default build up <see cref="Board"/>.</returns>
        public static Board Default()
        {
            Board board = Empty();
            // black
            board[new Position(0, 0)] = new Rock(Color.Black);
            board[new Position(1, 0)] = new Knight(Color.Black);
            board[new Position(2, 0)] = new Bishop(Color.Black);
            board[new Position(3, 0)] = new Queen(Color.Black);
            board[new Position(4, 0)] = new King(Color.Black);
            board[new Position(5, 0)] = new Bishop(Color.Black);
            board[new Position(6, 0)] = new Knight(Color.Black);
            board[new Position(7, 0)] = new Rock(Color.Black);
            board[new Position(0, 1)] = new Pawn(Color.Black, Pawn.Direction.Down);
            board[new Position(1, 1)] = new Pawn(Color.Black, Pawn.Direction.Down);
            board[new Position(2, 1)] = new Pawn(Color.Black, Pawn.Direction.Down);
            board[new Position(3, 1)] = new Pawn(Color.Black, Pawn.Direction.Down);
            board[new Position(4, 1)] = new Pawn(Color.Black, Pawn.Direction.Down);
            board[new Position(5, 1)] = new Pawn(Color.Black, Pawn.Direction.Down);
            board[new Position(6, 1)] = new Pawn(Color.Black, Pawn.Direction.Down);
            board[new Position(7, 1)] = new Pawn(Color.Black, Pawn.Direction.Down);
            // white
            board[new Position(0, 6)] = new Pawn(Color.White, Pawn.Direction.Up);
            board[new Position(1, 6)] = new Pawn(Color.White, Pawn.Direction.Up);
            board[new Position(2, 6)] = new Pawn(Color.White, Pawn.Direction.Up);
            board[new Position(3, 6)] = new Pawn(Color.White, Pawn.Direction.Up);
            board[new Position(4, 6)] = new Pawn(Color.White, Pawn.Direction.Up);
            board[new Position(5, 6)] = new Pawn(Color.White, Pawn.Direction.Up);
            board[new Position(6, 6)] = new Pawn(Color.White, Pawn.Direction.Up);
            board[new Position(7, 6)] = new Pawn(Color.White, Pawn.Direction.Up);
            board[new Position(0, 7)] = new Rock(Color.White);
            board[new Position(1, 7)] = new Knight(Color.White);
            board[new Position(2, 7)] = new Bishop(Color.White);
            board[new Position(3, 7)] = new Queen(Color.White);
            board[new Position(4, 7)] = new King(Color.White);
            board[new Position(5, 7)] = new Bishop(Color.White);
            board[new Position(6, 7)] = new Knight(Color.White);
            board[new Position(7, 7)] = new Rock(Color.White);
            return board;
        }

        private IPiece[,] _board;
        private int _size;
        private Color _colorToMove = Color.White;
        private int _turnCounter = 1;
        private ISet<Position> _enPassant;
        private Rochade _rochade;

        /// <summary>
        /// Initializes a new instance of the <see cref="Board"/> with the specified size.
        /// </summary>
        /// <param name="size">The size.</param>
        public Board(int size)
            : this(new IPiece[size, size])
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Board"/> with the specified raw data.
        /// </summary>
        /// <param name="board">The raw data.</param>
        public Board(IPiece[,] board)
            : this(board, Color.White, 1, new HashSet<Position>(), new Rochade())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Board"/> whith the specified raw data, color to move and turn counter.
        /// </summary>
        /// <param name="board">The board.</param>
        /// <param name="colorToMove">The color to move.</param>
        /// <param name="turnCounter">The turn counter.</param>
        public Board(IPiece[,] board, Color colorToMove, int turnCounter, ISet<Position> enPassant, Rochade rochade)
        {
            // only two-dimensional board data supported
            if (board.Rank != 2)
            {
                throw new ArgumentException("Only two-dimensional boards are supported.");
            }
            if (board.GetLength(0) != board.GetLength(1))
            {
                throw new ArgumentException("Only quadratic boards are supported.");
            }
            _board = board;
            _size = board.GetLength(0);
            _colorToMove = colorToMove;
            _turnCounter = turnCounter;
            _enPassant = enPassant;
            _rochade = rochade;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Board"/> coping the given one.
        /// </summary>
        /// <param name="copy">The copy.</param>
        public Board(Board copy)
            : this((IPiece[,]) copy._board.Clone(),
                   copy._colorToMove,
                   copy._turnCounter,
                   new HashSet<Position>(copy._enPassant),
                   new Rochade(copy._rochade)
                   )
        {
        }

        /// <summary>
        /// Gets or sets the <see cref="ChessToys.IPiece"/> with the specified x and y.
        /// </summary>
        /// <value>The <see cref="IPiece"/> to set.</value>
        public IPiece this[Position position]
        {
            get { return _board[position.X, position.Y]; }
            set { _board[position.X, position.Y] = value; }
        }

        /// <summary>
        /// Gets the size.
        /// </summary>
        /// <value>The size.</value>
        public int Size
        {
            get { return _size; }
        }

        public Color ColorToMove 
        {
            get { return _colorToMove; }
        }

        public ISet<Position> EnPassant
        {
            get { return _enPassant; }
        }

        public Rochade Rochade
        {
            get { return _rochade; }
        }

        /// <summary>
        /// Gets the FEN (Forsyth-Edwards-Notation).
        /// Further reading: http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
        /// </summary>
        /// <value>The FEN.</value>
        public String Fen
        {
            get
            {
                // string builder
                StringBuilder builder = new StringBuilder();
                // first group - field
                for (int y = 0; y < Size; y++)
                {
                    int blankCounter = 0;
                    for (int x = 0; x < Size; x++)
                    {
                        Position position = new Position(x, y);
                        IPiece piece = this[position];
                        if (piece != null)
                        {
                            // append blanks
                            if (blankCounter != 0)
                            {
                                builder.Append(blankCounter);
                                blankCounter = 0;
                            }
                            // append piece sign
                            builder.Append(piece.Sign);
                        }
                        else
                        {
                            blankCounter++;
                        }
                    }
                    // append blanks
                    if (blankCounter != 0)
                    {
                        builder.Append(blankCounter);
                        blankCounter = 0;
                    }
                    if (y < Size - 1)
                    {
                        builder.Append("/");
                    }
                }
                // second group - player
                builder.Append(" ");
                if (_colorToMove == Color.White)
                {
                    builder.Append("w");
                }
                else
                {
                    builder.Append("b");
                }
                // third group - rochade
                builder.Append(" ");
                builder.Append(_rochade.ToString());
                // TODO fourth group - en-passant
                builder.Append(" ");
                if (_enPassant.Count == 0)
                {
                    builder.Append("-");
                }
                else
                {
                    foreach (Position pos in _enPassant)
                    {
                        builder.Append(pos.ToString(this));
                    }
                }
                // TODO fifth group - half turn
                builder.Append(" ");
                builder.Append("0");
                // sixth group - turn
                builder.Append(" ");
                builder.Append(_turnCounter);
                // return result
                return builder.ToString();
            }
        }

        /// <summary>
        /// Applies the move.
        /// </summary>
        /// <param name="move">The move.</param>
        public void ApplyMove(Move move)
        {
            // get piece to move
            IPiece piece = this[move.Start];
            // is there a piece
            if (piece == null)
            {
                throw new ArgumentException(String.Format("At {0} is no piece to move.", move.Start));
            }
            // has the piece to move the right color
            if (!piece.Color.Equals(_colorToMove))
            {
                throw new ArgumentException(String.Format("{0} at {1} is {2}, but {3} is at move.", piece, move.Start, piece.Color, _colorToMove));
            }
            // disallow rochades
            {
                // white king moved -> white rochades not more allowed
                if (PieceUtils.Is(piece, typeof(King), Color.White))
                {
                    _rochade.WhiteKingSide = false;
                    _rochade.WhiteQueenSide = false;
                }
                // black king moved -> black rochades not more allowed
                if (PieceUtils.Is(piece, typeof(King), Color.Black))
                {
                    _rochade.BlackKingSide = false;
                    _rochade.BlackQueenSide = false;
                }
                if (PieceUtils.Is(piece, typeof(Rock), Color.White))
                {
                    if (move.Start == new Position(0, 7))
                    {
                        _rochade.WhiteQueenSide = false;
                    }
                    else if (move.Start == new Position(7, 7))
                    {
                        _rochade.WhiteKingSide = false;
                    }
                }
                else if (PieceUtils.Is(piece, typeof(Rock), Color.Black))
                {
                    if (move.Start == new Position(0, 0))
                    {
                        _rochade.BlackQueenSide = false;
                    }
                    else if (move.Start == new Position(7, 0))
                    {
                        _rochade.BlackKingSide = false;
                    }
                }
            }
            // move piece
            this[move.End] = this[move.Start];
            this[move.Start] = null;
            // en-passant
            if (PieceUtils.Is(this[move.End], typeof(Pawn)) && Math.Abs(move.Start.Y - move.End.Y) == 2)
            {
                int y = -1;
                Pawn pawn = this[move.End] as Pawn;
                switch (pawn.PlayDirection)
                {
                    case Pawn.Direction.Up:
                        y = 5;
                        break;
                    case Pawn.Direction.Down:
                        y = 2;
                        break;
                }
                _enPassant.Add(new Position(move.Start.X, y));
            }
            // pawn promotion
            if (move.GetType() == typeof(PawnPromotionMove))
            {
                // get promotion data
                PawnPromotionMove pawnPromotion = move as PawnPromotionMove;
                // promote pawn
                this[pawnPromotion.End] = pawnPromotion.PromoteTo;
            }
            // rochade move
            if (move.GetType() == typeof(RochadeMove))
            {
                // get rock move
                RochadeMove rochadeMove = move as RochadeMove;
                this[rochadeMove.RockMove.End] = this[rochadeMove.RockMove.Start];
                this[rochadeMove.RockMove.Start] = null;
            }
            // increment turn counter if black moved
            if (_colorToMove == Color.Black)
            {
                _turnCounter++;
            }
            // toggle color to move
            _colorToMove = _colorToMove.Toggle();
            // check for check mate
            if (CheckUtils.IsCheckMate(this, ColorToMove))
            {
                // ends the game
                throw new CheckMateException(this, ColorToMove);
            }
        }

        public void ToConsole()
        {
            for (int y = 0; y < Size; y++)
            {
                Console.Write(String.Format("  {0} ", Size - y));
                for (int x = 0; x < Size; x++)
                {
                    IPiece piece = this[new Position(x, y)];
                    if (piece == null)
                    {
                        Console.Write("_");
                    }
                    else
                    {
                        Console.Write(piece.Sign);
                    }
                }
                Console.WriteLine();
            }
            Console.WriteLine();
            Console.Write("    ");
            for (char c = 'a'; c < 'a' + Size; c++)
            {
                Console.Write(c);
            }
            Console.WriteLine();
        }

        /// <inheritdoc />
        public override string ToString()
        {
            return Fen;
        }

        /// <inheritdoc />
        public override bool Equals(object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (obj.GetType() != typeof(Board))
            {
                return false;
            }
            Board other = obj as Board;
            return Fen.Equals(other.Fen);
        }

        /// <inheritdoc />
        public override int GetHashCode()
        {
            return Fen.GetHashCode();
        }

        /// <inheritdoc />
        public IEnumerator<Position> GetEnumerator()
        {
            return new BoardEnumerator(this);
        }

        /// <inheritdoc />
        IEnumerator IEnumerable.GetEnumerator()
        {
            return new BoardEnumerator(this);
        }
    }
}
